﻿// //   Copyright 2007-2011 Comdiv (F. Sadykov) - http://code.google.com/u/fagim.sadykov/
// //   Supported by Media Technology LTD 
// //    
// //   Licensed under the Apache License, Version 2.0 (the "License");
// //   you may not use this file except in compliance with the License.
// //   You may obtain a copy of the License at
// //    
// //        http://www.apache.org/licenses/LICENSE-2.0
// //    
// //   Unless required by applicable law or agreed to in writing, software
// //   distributed under the License is distributed on an "AS IS" BASIS,
// //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// //   See the License for the specific language governing permissions and
// //   limitations under the License.
// //   
// //   MODIFICATIONS HAVE BEEN MADE TO THIS FILE

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using Comdiv.QWeb.Serialization.Extensions;

namespace Comdiv.QWeb.Serialization.Internals {
	internal class JsSerializerImpl : ISerializerImpl {
		private readonly Stack<objectSerializerState> _statestack = new Stack<objectSerializerState>();

		private objectSerializerState Current {
			get {
				return 0 == _statestack.Count ? objectSerializerState.undef : _statestack.Peek();
			}
		}

		#region ISerializerImpl Members

		public TextWriter output { get; set; }

		public void begin(string name) {
		}

		public void end() {
		}

		public void beginObject(string name) {
			_statestack.Push(objectSerializerState.obj);

			output.Write("{");
		}

		public void endObject() {
			var s = _statestack.Pop();
			if (s != objectSerializerState.obj) throw new Exception("cannot close object here");
			output.Write("}");
		}

		public void beginObjectItem(string name, bool isfinal) {
			if (Current == objectSerializerState.obj) {
				output.Write(Literal(name) + ": ");
			}
			else {
				throw new Exception("cannot write object items here");
			}
		}

		public void endObjectItem(bool last) {
			if (Current == objectSerializerState.obj) {
				output.Write(", ");
			}
			else
				throw new Exception("cannot finish object items here");
		}

		public void writeFinal(object value) {
			if (null == value) {
				output.Write("null");
			}
			else if (value is JsRaw) {
				output.Write(value.ToString());
			}
			else if (value is string) {
				output.Write("'" + escape((string) value) + "'");
			}
			else if (value is DateTime) {
				var d = (DateTime) value;
				output.Write(string.Format("new Date({0},{1},{2},{3},{4},{5})", d.Year, d.Month - 1, d.Day, d.Hour, d.Minute,
				                           d.Second));
			}
			else if (value is int || value is long) {
				output.Write(value.ToString());
			}
			else if (value is double || value is decimal || value is float) {
				output.Write(Convert.ToDouble(value).ToString("0.########", CultureInfo.InvariantCulture));
			}
			else if (value is bool) {
				output.Write(value.ToString().ToLower());
			}
			else {
				output.Write("'" + escape(value.ToString()) + "'");
			}
		}


		public void beginDictionary(string name) {
			beginObject(name);
		}

		public void endDictionary() {
			endObject();
		}

		public void beginDictionaryEntry(string name) {
			beginObjectItem(name, false);
		}

		public void endDictionaryEntry(bool last) {
			endObjectItem(last);
		}

		public void beginArray(string name) {
			_statestack.Push(objectSerializerState.array);
			output.Write("[");
		}

		public void endArray() {
			var r = _statestack.Pop();
			if (r != objectSerializerState.array) {
				throw new Exception("cannot close array here");
			}
			output.Write("]");
		}

		public void beginArrayEntry(int idx) {
		}

		public void endArrayEntry(bool last) {
			if (Current == objectSerializerState.array) {
				output.Write(", ");
			}
			else
				throw new Exception("cannot finish array items here");
		}

		public void flush() {
			output.Flush();
		}

		#endregion

		private string Literal(string name) {
			var esc = escape(name);
			if (
				esc.Trim() == name &&
				esc != "" &&
				!Regex.IsMatch(esc, @"\s")
				&& Regex.IsMatch(esc, @"^\D")
				&& !esc.Contains(".")
				) return name;
			return "'" + esc + "'";
		}

		private string escape(string str) {
			return str.Replace("\\", "\\\\").Replace("'", "\\'").Replace("\r", "\\r").Replace("\t", "\\t").Replace("\n", "\\n");
		}
	}
}